/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.help;

import ws.quokka.core.repo_spi.RepoArtifactId;
import ws.quokka.core.repo_spi.RepoDependency;
import ws.quokka.core.repo_spi.RepoPath;
import ws.quokka.core.repo_spi.RepoPathSpec;
import ws.quokka.core.util.Strings;
import ws.quokka.core.util.URLs;
import ws.quokka.core.util.xml.Document;
import ws.quokka.core.util.xml.Element;

import java.net.URL;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;


/**
 *
 */
public class PluginHelpGenerator extends AbstractHelpGenerator {
    //~ Methods --------------------------------------------------------------------------------------------------------

    protected URL getMetadataURL() {
        return URLs.toURL(artifact.getLocalCopy(), plugin.getPluginPath(artifact.getId()));
    }

    protected void addContent(Document out, Element bodyEl) {
        Element pluginEl = metaDocument.getRoot();
        List targets = pluginEl.getChild("targets").getChildren("target");
        List mainTargets = new ArrayList();
        List minorTargets = new ArrayList();
        List abstractTargets = new ArrayList();
        List templateTargets = new ArrayList();

        for (Iterator i = targets.iterator(); i.hasNext();) {
            Element targetEl = (Element)i.next();

            if ("true".equals(targetEl.getAttribute("template"))) {
                templateTargets.add(targetEl);
            } else if ("true".equals(targetEl.getAttribute("abstract"))
                    || ((pluginEl.getAttribute("class") == null) && (targetEl.getAttribute("abstract") == null))) {
                abstractTargets.add(targetEl);
            } else if ("true".equals(targetEl.getAttribute("main"))) {
                mainTargets.add(targetEl);
            } else {
                minorTargets.add(targetEl);
            }
        }

        List all = new ArrayList();
        addTargetsSummary(bodyEl, "Main Targets", mainTargets, all);
        addTargetsSummary(bodyEl, "Minor Targets", minorTargets, all);
        addTargetsSummary(bodyEl, "Template Targets", templateTargets, all);
        addTargetsSummary(bodyEl, "Abstract Targets", abstractTargets, all);

        copyContent(out, bodyEl, "after-summary");

        addSharedDefaultProperties(bodyEl, pluginEl);

        for (Iterator i = all.iterator(); i.hasNext();) {
            Element targetEl = (Element)i.next();
            addTargetDetail(bodyEl, pluginEl, targetEl);
        }
    }

    protected String getVersionText() {
        String nameSpace = metaDocument.getRoot().getAttribute("name-space");

        return super.getVersionText() + ((nameSpace == null) ? "" : (", namespace=" + nameSpace));
    }

    private void addSharedDefaultProperties(Element bodyEl, Element pluginEl) {
        bodyEl = bodyEl.addChild("div").setAttribute("class", "properties");

        Element propertiesEl = pluginEl.getChild("properties");

        Element headingEl = bodyEl.addChild("h2").addText("Shared Default Properties");
        Element tableEl = bodyEl.addChild("table").setAttribute("class", "properties");
        boolean added = false;

        if (propertiesEl != null) {
            for (Iterator i = propertiesEl.getChildren("property").iterator(); i.hasNext();) {
                Element propertyEl = (Element)i.next();
                List targets = Strings.commaSepList(propertyEl.getAttribute("targets"));

                String name = propertyEl.getAttribute("name");

                if (!name.startsWith("prefix.")) {
                    added = true;

                    Element trEl = tableEl.addChild("tr");
                    trEl.addChild("td").addText(name);
                    trEl.addChild("td").addText(getPropertyValue(propertyEl));
                    trEl.addChild("td").addText(Strings.join(targets.iterator(), ", "));
                }
            }
        }

        if (!added) {
            bodyEl.getElement().removeChild(headingEl.getElement());
            bodyEl.getElement().removeChild(tableEl.getElement());
        }
    }

    private String getPropertyValue(Element propertyEl) {
        String value = propertyEl.getAttribute("value");

        if (value == null) {
            value = propertyEl.getText();
        }

        return value;
    }

    protected void addFooterContent(Document out, Element outBodyEl) {
        outBodyEl.addChild("hr");
        addPluginPaths(outBodyEl.addChild("div").setAttribute("id", "pluginPaths"));
    }

    private void addPluginPaths(Element divEl) {
        divEl.addChild("h2", "Plugin Paths");

        // Display the paths
        for (Iterator i = artifact.getPaths().iterator(); i.hasNext();) {
            RepoPath path = (RepoPath)i.next();
            plugin.addPath(divEl, path, plugin.getResolver().resolvePath(path.getId(), artifact).getArtifacts(), "p",
                false);

            // Add the optional dependencies
            Set optional = new TreeSet();

            for (Iterator j = artifact.getDependencies().iterator(); j.hasNext();) {
                RepoDependency dependency = (RepoDependency)j.next();

                for (Iterator k = dependency.getPathSpecs().iterator(); k.hasNext();) {
                    RepoPathSpec pathSpec = (RepoPathSpec)k.next();

                    if (pathSpec.getTo().equals(path.getId()) && !pathSpec.isMandatory().booleanValue()) {
                        optional.add(dependency.getId());
                    }
                }
            }

            if (optional.size() != 0) {
                Element pEl = divEl.addChild("p");
                pEl.addChild("b").addText(path.getId());
                pEl.addText(": Optional dependencies");
                divEl = divEl.addChild("div").setAttribute("class", "indent");

                for (Iterator j = optional.iterator(); j.hasNext();) {
                    RepoArtifactId id = (RepoArtifactId)j.next();
                    plugin.addId(divEl.addChild("p"), id, false, false);
                }
            }
        }
    }

    private void addTargetDetail(Element bodyEl, Element pluginEl, Element targetEl) {
        bodyEl.addChild("hr");

        Element divEl = bodyEl.addChild("div").setAttribute("class", "detail-summary");

        String name = targetEl.getAttribute("name");
        Element h2el = divEl.addChild("h2");
        h2el.addChild("a").setAttribute("name", name).addChild("span");
        h2el.addText(name);

        String description = targetEl.getAttribute("description");

        if (description != null) {
            divEl.addChild("p").addText(description);
        }

        Element ulEl = divEl.addChild("ul");
        addAttribute(ulEl, targetEl, "abstract");
        addAttribute(ulEl, targetEl, "alias");
        addAttribute(ulEl, targetEl, "depends");
        addAttribute(ulEl, targetEl, "prefix");
        addAttribute(ulEl, targetEl, "template-name");
        addAttribute(ulEl, targetEl, "template");
        addAttribute(ulEl, targetEl, "enabled-by-default");

        String implementsVal = targetEl.getAttribute("implements");

        if (implementsVal != null) {
            Element liEl = ulEl.addChild("li");
            liEl.addChild("b").addText("Implements: ");
            addImplementsRef(liEl, implementsVal);
        }

        if (ulEl.getChildren("li").size() == 0) {
            divEl.getElement().removeChild(ulEl.getElement()); // Empty ul not valid xhtml
        }

        // Insert per target content here
        copyContent(new Document(bodyEl.getElement().getOwnerDocument()), bodyEl, name);

        Map defaultProperties = getDefaultProperties(pluginEl, targetEl);

        if (defaultProperties.size() != 0) {
            bodyEl.addChild("p").addChild("b").addText("Default Properties");
            targetEl.getChildren("property");

            Element tableEl = bodyEl.addChild("table").setAttribute("class", "properties");

            for (Iterator i = defaultProperties.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry)i.next();
                Element trEl = tableEl.addChild("tr");
                trEl.addChild("td").addText((String)entry.getKey());
                trEl.addChild("td").addText((String)entry.getValue());
            }
        }
    }

    private Map getDefaultProperties(Element pluginEl, Element targetEl) {
        String prefix = targetEl.getAttribute("prefix");
        Map defaults = new TreeMap();

        for (Iterator i = targetEl.getChildren("property").iterator(); i.hasNext();) {
            Element propertyEl = (Element)i.next();

            String name = propertyEl.getAttribute("name");
            defaults.put(fixName(prefix, name), getPropertyValue(propertyEl));
        }

        Element propertiesEl = pluginEl.getChild("properties");

        if (propertiesEl != null) {
            for (Iterator i = propertiesEl.getChildren("property").iterator(); i.hasNext();) {
                Element propertyEl = (Element)i.next();
                List targets = Strings.commaSepList(propertyEl.getAttribute("targets"));

                if ((targets.size() == 0) || targets.contains(targetEl.getAttribute("name"))) {
                    String name = propertyEl.getAttribute("name");

                    if (name.startsWith("prefix.") || ((prefix != null) && name.startsWith(prefix + "."))) {
                        defaults.put(fixName(prefix, name), getPropertyValue(propertyEl));
                    }
                }
            }
        }

        return defaults;
    }

    private String fixName(String prefix, String name) {
        if (name.startsWith("prefix.")) {
            return name.substring(7);
        }

        if ((prefix != null) && name.startsWith(prefix + ".")) {
            return name.substring(prefix.length() + 1);
        }

        return name;
    }

    private void addAttribute(Element ulEl, Element targetEl, String attribute) {
        String value = targetEl.getAttribute(attribute);

        if (value != null) {
            Element liEl = ulEl.addChild("li");
            liEl.addChild("b").addText(attribute + ": ");
            liEl.addText(value);
        }
    }

    private void addTargetsSummary(Element outBodyEl, String title, List targets, List all) {
        if (targets.size() == 0) {
            return;
        }

        outBodyEl.addChild("h2").addText(title);

        Element tableEl = outBodyEl.addChild("table");

        for (Iterator i = targets.iterator(); i.hasNext();) {
            Element targetEl = (Element)i.next();
            Element rowEl = tableEl.addChild("tr");
            String name = targetEl.getAttribute("name");
            rowEl.addChild("td").addChild("a").setAttribute("href", "#" + name).addText(name);

            String description = targetEl.getAttribute("description");
            description = (description == null) ? "" : description.trim();

            String implementsVal = targetEl.getAttribute("implements");

            Element tdEl = rowEl.addChild("td").addText(description);

            if (implementsVal != null) {
                if (!description.equals("")) {
                    tdEl.addText(description.endsWith(".") ? " " : ". ");
                }

                addImplementsRef(tdEl.addText("Implements "), implementsVal);
            }
        }

        all.addAll(targets);
    }

    private void addImplementsRef(Element element, String implementsVal) {
        String[] tokens = Strings.split(implementsVal, ":");
        element.addText(tokens[0] + ":" + tokens[2]);

        // TODO: reinstate link when it's valid
//        element.addChild("a").setAttribute("href", tokens[0] + ".html#" + tokens[2]).addText(tokens[0] + ":"
//                + tokens[2]);
    }
}
